#include "bg_hooks.h"
#include <udis86.h>
#include "q_shared.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <assert.h>
#include <stdlib.h>

// This file was not made by me -- the majority of the contents of this file belong to BobaFett and/or Deathspike.

// ==================================================
// UnlockMemory (WIN32 & Linux compatible)
// --------------------------------------------------
// Makes the memory at address writable for at least size bytes.
// Returns 1 if successful, returns 0 on failure.
// ==================================================
qboolean UnlockMemory( int address, int size )
{
#ifdef _WIN32
	DWORD dummy;
	return ( VirtualProtect( (LPVOID)address, size, PAGE_EXECUTE_READWRITE, &dummy ) != 0 );
#else
	// Linux is a bit more tricky
	int ret;
	int page1, page2;
	page1 = address & ~( getpagesize() - 1);
	page2 = (address+size) & ~( getpagesize() - 1);

	if ( page1 == page2 )
		return (mprotect( (char *)page1, getpagesize(), PROT_READ | PROT_WRITE | PROT_EXEC ) == 0);
	else
	{
		ret = mprotect( (char *)page1, getpagesize(), PROT_READ | PROT_WRITE | PROT_EXEC );
		if ( ret ) return 0;
		return (mprotect( (char *)page2, getpagesize(), PROT_READ | PROT_WRITE | PROT_EXEC ) == 0);
	}
#endif
}

// ==================================================
// LockMemory (WIN32 & Linux compatible)
// --------------------------------------------------
// Makes the memory at address read-only for at least size bytes.
// Returns 1 if successful, returns 0 on failure.
// ==================================================
qboolean LockMemory( int address, int size )
{
#ifdef _WIN32
	DWORD dummy;
	return ( VirtualProtect( (LPVOID)address, size, PAGE_EXECUTE_READ, &dummy ) != 0 );
#else
	// Linux is a bit more tricky
	int ret;
	int page1, page2;
	page1 = address & ~( getpagesize() - 1);
	page2 = (address+size) & ~( getpagesize() - 1);

	if( page1 == page2 )
		return (mprotect( (char *)page1, getpagesize(), PROT_READ | PROT_EXEC ) == 0);
	else
	{
		ret = mprotect( (char *)page1, getpagesize(), PROT_READ | PROT_EXEC );
		if ( ret ) return 0;
		return (mprotect( (char *)page2, getpagesize(), PROT_READ | PROT_EXEC ) == 0);
	}
#endif
}

#ifdef PATCH_ENGINE
void PlaceHook( hookEntry_t *hook )
{
	bool success = false;

	if ( hook && (success = UnlockMemory( hook->hookPosition, 5 )) )
	{
		unsigned int forward = (unsigned int)((void*(*)())hook->hookForward)(); //i never want to see this line again
		memcpy( &hook->origBytes[0], (void *)hook->hookPosition, 5 );
		*(unsigned char *)(hook->hookPosition) = hook->hookOpcode;
		*(unsigned int *)(hook->hookPosition+1) = (forward) - ((unsigned int)(hook->hookPosition)+5);
		success = LockMemory( hook->hookPosition, 5 );
	}
#ifdef _DEBUG
	Com_Printf( success ? va( "  %s\n", hook->name )
						: va( "^1Warning: Failed to place hook: %s\n", hook->name ) );
#endif
}

void RemoveHook( const hookEntry_t *hook )
{
	bool success = false;

	if ( hook && (success = UnlockMemory( hook->hookPosition, 5 )) )
	{
		memcpy( (void *)hook->hookPosition, hook->origBytes, 5 );
		success = LockMemory( hook->hookPosition, 5 );
	}
#ifdef _DEBUG
	Com_Printf( success ? va( "  %s\n", hook->name )
						: va( "^1Warning: Failed to remove hook: %s\n", hook->name ) );
#endif
}

#endif

PatchData_t *BG_PlacePatch( int type, unsigned int address, unsigned int destination ) {
	PatchData_t *patch = (PatchData_t *)malloc(sizeof(PatchData_t));
	//int addr = address;
	int sz = 0;

	ud_t ud;

	ud_init(&ud);
	ud_set_input_buffer(&ud, (uint8_t *)address, 48);
	ud_set_mode(&ud, 32);
	ud_set_pc(&ud, address);
	ud_set_syntax(&ud, NULL);

	while (ud_disassemble(&ud))
	{
		sz += ud_insn_len(&ud);
		if (sz >= 5) {
			break;
		}
	}

	assert(sz >= 5);

	if (sz < 5 || sz > 24) {
		// This really shouldnt ever happen, in the worst case scenario,
		// the block is 20 bytes (4 + 16), so if we hit 24, something went wrong
		return NULL;
	}

	patch->addr = address;
	patch->size = sz;
	memcpy(patch->origbytes, (const void *)address, sz);
	UnlockMemory(address, sz); // Make the memory writable
	*(unsigned char *)address = type == PATCH_JUMP ? 0xE9 : 0xE8;
	*(unsigned int *)(address+1) = destination - (address + 5);
	memset((void *)(address+5),0x90,sz-5);	// Nop the rest
	LockMemory(address, sz);
	return patch;
}

void BG_RemovePatch(PatchData_t **patch) {
	if (!*patch)
		return;
	UnlockMemory((*patch)->addr, (*patch)->size);
	memcpy((void *)(*patch)->addr, (*patch)->origbytes, (*patch)->size);
	LockMemory((*patch)->addr, (*patch)->size);
	*patch = 0;
}